﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class HotPathcManager : Singleton<HotPathcManager>
{
    private MonoBehaviour m_Mono;
    private string m_CurVersion;
    public string CurVersion
    {
        get { return m_CurVersion; }
    }
    public string m_CurPackName;
    private Pathces m_CurrentPatches; 
    public Pathces CurrentPatches
    {
        get { return m_CurrentPatches; }
    }
    private string m_ServerXmlPath = Application.persistentDataPath + "/ServerInfo.xml";
    private string m_UnPackPath = Application.persistentDataPath + "/Origin";
    private string m_DownLoadPath = Application.persistentDataPath + "/DownLoad";
    private string m_LocalXmlPath = Application.persistentDataPath + "/LocalInfo.xml";
    private ServerInfo m_ServerInfo;
    private ServerInfo m_LocalInfo;
    private VersionInfo m_GameVersion;
    // 所有热更的资源
    private Dictionary<string,Patch> m_HotFixDic = new Dictionary<string, Patch>();
    // 所有需要下载的资源
    private List<Patch> m_DownloadList = new List<Patch>();
    // 所有需要下载的东西的Dic
    private Dictionary<string,Patch> m_DownLoadDic = new Dictionary<string, Patch>();
    // 服务器上的资源名对应的MD5，用于下载后的MD5 检验
    private Dictionary<string,string> m_DownLoadMD5Dic = new Dictionary<string, string>();
    // 计算需要解压的文件
    private List<string> m_UnPackedList = new List<string>();
    // 原包记录的MD5码
    private Dictionary<string,ABMD5Base> m_PackedMd5 = new Dictionary<string, ABMD5Base>();
    // 需要下载的资源总个数 
    public int LoadFileCount { get; set; } = 0;
    // 需要下载的资源总大小Kb
    public float LoadSumSize { get; set; } = 0;
    
    // 储存已经下载的资源
    public List<Patch> m_AlreadyDownLoadList = new List<Patch>();

    // 是否已经开始下载
    private bool m_startDownLoad = false;
    public bool StartDownload
    {
        get { return m_startDownLoad; }
    }
    //尝试重新下载次数
    private int m_TryDownCount = 0;
    private const int DOWNLOADCOUNT = 4;
    //服务器列表获取错误回调
    public Action ServerInfoError;
    public Action<string> ItemError;
    public Action LoadOver;

    private DownLoadAssetBundle m_CurDownload = null;
    // 是否开始解压
    public bool StartUnPack = false;
    // 解压文件的总大小
    public float UnPackSumSize  {get;set;}= 0;
    // 已解压文件的大小
    public float AlreadyUnPackSize  {get;set;}= 0;
    public void Init(MonoBehaviour mono)
    {
        m_Mono = mono;
        ReadMD5();
    }

    /// <summary>
    /// 读取本地资源MD5码
    /// </summary>
    void ReadMD5()
    {
        m_PackedMd5.Clear();
        TextAsset md5 = Resources.Load<TextAsset>("ABMD5");
        if (md5 == null)
        {
            Debug.LogError("未读取到本地的MD5文件");
            return;
        }

        using (MemoryStream stream = new MemoryStream(md5.bytes))
        {
            BinaryFormatter bf = new BinaryFormatter();
            ABMD5 abmd5 = bf.Deserialize(stream) as ABMD5;
            for (int i = 0; i < abmd5.ABMD5List.Count; i++)
            {
                m_PackedMd5.Add(abmd5.ABMD5List[i].Name,abmd5.ABMD5List[i]);
            }
        }
    }
    
    /// <summary>
    /// 计算需要解压的文件
    /// </summary>
    /// <returns></returns>
    public bool ComputeUnPackFile()
    {
        if (!Directory.Exists(m_UnPackPath))
        {
            Directory.CreateDirectory(m_UnPackPath);
        }
        m_UnPackedList.Clear();
        foreach (string fileName in m_PackedMd5.Keys)
        {
            string filePath = m_UnPackPath + "/" + fileName;
            if (File.Exists(filePath))
            {
                string md5 = MD5Manager.Instance.BuildFileMd5(filePath);
                if (m_PackedMd5[fileName].Md5 != md5)
                {
                    m_UnPackedList.Add(fileName);
                }
            }
            else
            {
                m_UnPackedList.Add(fileName);
            }
        }

        foreach (string fileName in m_UnPackedList)
        {
            if (m_PackedMd5.ContainsKey(fileName))
            {
                UnPackSumSize += m_PackedMd5[fileName].Size;
            }
        }
        return m_UnPackedList.Count>0;
    }

    /// <summary>
    /// 获取解压进度
    /// </summary>
    /// <returns></returns>
    public float GetUnpackProgress()
    {
        return AlreadyUnPackSize / UnPackSumSize;
    }

    /// <summary>
    /// 开始解压文件
    /// </summary>
    /// <param name="callBack"></param>
    public void StartUnPackFile(Action callBack)
    {
        StartUnPack = true;
        Debug.Log("开始解压");
        m_Mono.StartCoroutine(UnPackToPersistentDataPath(callBack));
    }

    /// <summary>
    /// todo 解压文件从steamingassets文件夹到数据持久文件夹 
    /// </summary>
    /// <param name="callBack"></param>
    /// <returns></returns>
    IEnumerator UnPackToPersistentDataPath(Action callBack)
    {
        foreach (string fileName in m_UnPackedList)
        {
            UnityWebRequest unityWebRequest = UnityWebRequest.Get(Application.streamingAssetsPath+"/"+fileName);
            unityWebRequest.timeout = 30;
            yield return unityWebRequest.SendWebRequest();
            if (unityWebRequest.isNetworkError)
            {
                Debug.LogError("解压文件出错：" +fileName);
            }
            else
            {
                byte[] bytes = unityWebRequest.downloadHandler.data;
                FileTool.CreateFile(m_UnPackPath+"/"+fileName,bytes);
            }

            if (m_PackedMd5.ContainsKey(fileName))
            {
                AlreadyUnPackSize += m_PackedMd5[fileName].Size;
            }
            unityWebRequest.Dispose();
        }

        if (callBack != null)
        {
            callBack();
        }
        Debug.Log("解压完成");
        StartUnPack = false;
    }
    public string ComputeABPath(string name)
    {
        Patch path = null;
        m_HotFixDic.TryGetValue(name, out path);
        if (path != null)
        {
            return m_DownLoadPath + "/" + name;
        }
        return "";
    }

    public void CheckVersion(Action<bool> hotCallBack = null)
    {
        m_HotFixDic.Clear();
        // 读取本地版本信息
        ReadVersion();
        // 读取服务器版本信息 然后进行对比 
        m_Mono.StartCoroutine(ReadXml(() =>
        {
            if (m_ServerInfo == null)
            {
                if (ServerInfoError != null)
                {
                    ServerInfoError();
                }
                return;
            }

            foreach (VersionInfo versionInfo in m_ServerInfo.GameVersion)
            {
                if (versionInfo.Version == m_CurVersion)
                {
                    m_GameVersion = versionInfo;
                    break;
                }
            }

            GetHotAB();
            if (CheckLocalAndServerPath())
            {
                ComputeDownload();
                // todo 建议下载完之后再去更新
                if (File.Exists(m_ServerXmlPath))
                {
                    if (File.Exists(m_LocalXmlPath))
                    {
                        File.Delete(m_LocalXmlPath);
                    }
                    File.Move(m_ServerXmlPath,m_LocalXmlPath);
                }
                
            }
            else
            {
                ComputeDownload();
            }
            
            LoadFileCount = m_DownloadList.Count;
            LoadSumSize = m_DownloadList.Sum(x => x.Size);
            
            if (hotCallBack != null)
            {
                hotCallBack(m_DownloadList.Count>0);
            }
        }));
    }

    /// <summary>
    /// 检查本地资源与服务器下载列表信息是否一致，主要用于下载一半退出再进入游戏，剩下的部分下载，断点续传
    /// </summary>
    void CheckLocalResource()
    {
        m_DownloadList.Clear();
        m_DownLoadDic.Clear();
        if (m_GameVersion != null && m_GameVersion.Pathces != null && m_GameVersion.Pathces.Length > 0)
        {
            m_CurrentPatches = m_GameVersion.Pathces[m_GameVersion.Pathces.Length - 1];
            if (m_CurrentPatches.Files != null&&m_CurrentPatches.Files.Count>0)
            {
                foreach (Patch patch in m_CurrentPatches.Files)
                {
                    if ((Application.platform == RuntimePlatform.WindowsPlayer ||
                         Application.platform == RuntimePlatform.WindowsEditor)&&patch.Platform.Contains("StandaloneWindows64"))
                    {
                        AddDownLoadList(patch);
                    }
                    else if((Application.platform == RuntimePlatform.Android ||
                             Application.platform == RuntimePlatform.WindowsEditor)&&patch.Platform.Contains("Android"))
                    {
                        AddDownLoadList(patch);
                    }
                    else if((Application.platform == RuntimePlatform.IPhonePlayer ||
                             Application.platform == RuntimePlatform.WindowsEditor)&&patch.Platform.Contains("IOS"))
                    {
                        AddDownLoadList(patch);
                    }
                }
            }
        }
    }
    
    /// <summary>
    /// 检查本地热更信息与服务器热更信息是否需要下载
    /// </summary>
    /// <returns></returns>
    bool CheckLocalAndServerPath()
    {
        if (!File.Exists(m_LocalXmlPath))
        {
            return true;
        }
        m_LocalInfo = BinarySerializeOpt.XmlDeserialize(m_LocalXmlPath,typeof(ServerInfo)) as ServerInfo;
        VersionInfo localGameVersion = null;
        if (m_LocalInfo != null)
        {
            foreach (VersionInfo versionInfo in m_LocalInfo.GameVersion)
            {
                if (versionInfo.Version == m_CurVersion)
                {
                    localGameVersion = m_GameVersion;
                    break;
                }
            }
        }

        if (localGameVersion != null && m_GameVersion.Pathces != null && m_GameVersion.Pathces.Length > 0 &&
            localGameVersion.Pathces != null && m_GameVersion.Pathces[m_GameVersion.Pathces.Length - 1].Version !=
            localGameVersion.Pathces[localGameVersion.Pathces.Length - 1].Version)
        {
            return true;
        }

        return false;
    }
    void ReadVersion()
    {
        TextAsset versionText = Resources.Load<TextAsset>("Version");
        if (versionText == null)
        {
            Debug.LogError("未读到本地版本信息");
            return;
        }

        string[] all = versionText.text.Split('\n');
        if (all.Length > 0)
        {
            string[] infoList = all[0].Split(';');
            if (infoList.Length >= 2)
            {
                m_CurVersion = infoList[0].Split('|')[1];
                m_CurPackName = infoList[1].Split('|')[1];
            }
        }
    }

    IEnumerator ReadXml(Action callBack)
    {
        // todo 在这里修改ServerInfo.xml路径
        string xmlUrl = "http://192.168.31.152:7788/JoeHotFit/ServerInfo.xml";
        UnityWebRequest request = UnityWebRequest.Get(xmlUrl);
        request.timeout = 10;
        yield return request.SendWebRequest();
        if (request.isNetworkError)
        {
            Debug.LogError("Download Error" + request.error);
        }
        else
        {
            FileTool.CreateFile(m_ServerXmlPath,request.downloadHandler.data);
            Debug.Log(request.downloadHandler.text);
            if (File.Exists(m_ServerXmlPath))
            {
                m_ServerInfo = BinarySerializeOpt.XmlDeserialize(m_ServerXmlPath, typeof(ServerInfo)) as ServerInfo;
            }
            else
            {
                Debug.LogError("热更错误");
            }

            if (callBack != null)
            {
                callBack();
            }
        }
    }

    void GetHotAB()
    {
        if (m_GameVersion != null && m_GameVersion.Pathces != null && m_GameVersion.Pathces.Length > 0)
        {
            Pathces lastPatches = m_GameVersion.Pathces[m_GameVersion.Pathces.Length - 1];
            if (lastPatches != null && lastPatches.Files != null)
            {
                foreach (Patch patch in lastPatches.Files)
                {
                    m_HotFixDic.Add(patch.Name,patch);
                }
            }
        }
    }

    void ComputeDownload()
    {
        m_DownloadList.Clear();
        m_DownLoadDic.Clear();
        m_DownLoadMD5Dic.Clear();
        if (m_GameVersion != null && m_GameVersion.Pathces != null && m_GameVersion.Pathces.Length > 0)
        {
            m_CurrentPatches = m_GameVersion.Pathces[m_GameVersion.Pathces.Length - 1];
            if (m_CurrentPatches.Files != null&&m_CurrentPatches.Files.Count>0)
            {
                foreach (Patch patch in m_CurrentPatches.Files)
                {
                    if ((Application.platform == RuntimePlatform.WindowsPlayer ||
                        Application.platform == RuntimePlatform.WindowsEditor)&&patch.Platform.Contains("StandaloneWindows64"))
                    {
                        AddDownLoadList(patch);
                    }
                    else if((Application.platform == RuntimePlatform.Android ||
                             Application.platform == RuntimePlatform.WindowsEditor)&&patch.Platform.Contains("Android"))
                    {
                        AddDownLoadList(patch);
                    }
                    else if((Application.platform == RuntimePlatform.IPhonePlayer ||
                             Application.platform == RuntimePlatform.WindowsEditor)&&patch.Platform.Contains("IOS"))
                    {
                        AddDownLoadList(patch);
                    }
                }
            }
        }
    }

    void AddDownLoadList(Patch patch)
    {
        string filePath = m_DownLoadPath + "/" + patch.Name;
        // 存在这个文件时进行对比看是否与服务器MD5码一致，不一致放到下载队列 
        if (File.Exists(filePath))
        {
            string md5 = MD5Manager.Instance.BuildFileMd5(filePath);
            if (md5 != patch.Md5)
            {
                m_DownloadList.Add(patch);
                m_DownLoadDic.Add(patch.Name,patch);
                m_DownLoadMD5Dic.Add(patch.Name,patch.Md5);
            }
        }
        else
        {
            m_DownloadList.Add(patch);
            m_DownLoadDic.Add(patch.Name,patch);
            m_DownLoadMD5Dic.Add(patch.Name,patch.Md5);
        }
    }

    /// <summary>
    /// 获取下载进度
    /// </summary>
    /// <returns></returns>
    public float GetProgress()
    {
        return  GetLoadSize()/LoadSumSize;
    }

    /// <summary>
    /// 获取已经下载的总大小
    /// </summary>
    /// <returns></returns>
    public float GetLoadSize()
    {
        float alreadySize = m_AlreadyDownLoadList.Sum(x => x.Size);
        float curAlreadySize = 0;
        if (m_CurDownload != null)
        {
            Patch patch = FindPatchByGamePath(m_CurDownload.FileName);
            if (patch != null && !m_AlreadyDownLoadList.Contains(patch))
            {
                curAlreadySize = m_CurDownload.GetProcess() * patch.Size;
            }
        }

        return alreadySize + curAlreadySize;
    }
    
    /// <summary>
    /// 开始下载AB包
    /// </summary>
    /// <param name="callBack"></param>
    /// <returns></returns>
    public IEnumerator StartDownLoadAB(Action callBack,List<Patch> allPatch = null)
    {
        m_AlreadyDownLoadList.Clear();
        m_startDownLoad = true;
        if (allPatch == null)
        {
            allPatch = m_DownloadList;
        }
        if (!Directory.Exists(m_DownLoadPath))
        {
            Directory.CreateDirectory(m_DownLoadPath);
        }
        List<DownLoadAssetBundle> downloadAssetBundles = new List<DownLoadAssetBundle>();
        foreach (Patch patch in allPatch)
        {
            downloadAssetBundles.Add(new DownLoadAssetBundle(patch.Url,m_DownLoadPath));
        }

        foreach (DownLoadAssetBundle downloadAssetBundle in downloadAssetBundles)
        {
            m_CurDownload = downloadAssetBundle;
            //  todo 最多可以开多少协程？
            yield return m_Mono.StartCoroutine(downloadAssetBundle.Download());
            Patch patch = FindPatchByGamePath(downloadAssetBundle.FileName);
            if (patch != null)
            {
                m_AlreadyDownLoadList.Add(patch);
            }
            downloadAssetBundle.Destory();
        }
        // MD5码校验 如果检验没通过，自动重新下载没通过的文件。重复下载计数达到一定次数后，反馈XX文件下载失败
        VerifyMD5(downloadAssetBundles,callBack);
    }

    void VerifyMD5(List<DownLoadAssetBundle> downLoadAssets,Action callBack)
    {
        List<Patch> downLoadList = new List<Patch>();
        foreach (DownLoadAssetBundle downLoad in downLoadAssets)
        {
            string md5 = "";
            if (m_DownLoadMD5Dic.TryGetValue(downLoad.FileName, out md5))
            {
                if (MD5Manager.Instance.BuildFileMd5(downLoad.SaveFilePath) != md5)
                {
                    Debug.Log(string.Format("此文件{0}MD5校验失败，即将重新下载", downLoad.FileName));
                    Patch patch = FindPatchByGamePath(downLoad.FileName);
                    if (patch != null)
                    {
                        downLoadList.Add(patch);
                    }
                }
            }
        }

        if (downLoadList.Count <= 0)
        {
            m_DownLoadMD5Dic.Clear();
            if (callBack != null)
            {
                m_startDownLoad = false;
                callBack();
            }
            if (LoadOver != null)
            {
                LoadOver();
            }
        }
        else
        {
            if (m_TryDownCount >= DOWNLOADCOUNT)
            {
                string allName = "";
                m_startDownLoad = false;
                foreach (Patch patch in downLoadList)
                {
                    allName += patch.Name + ";";
                }
                Debug.LogError("资源重复下载4次MD5校验都失败，请检查资源" + allName);
                if (ItemError != null)
                {
                    ItemError(allName);
                }
            }
            else
            {
                m_TryDownCount++;
                m_DownLoadMD5Dic.Clear();
                foreach (Patch patch in downLoadList)
                {
                    m_DownLoadMD5Dic.Add(patch.Name, patch.Md5);
                }
                //自动重新下载校验失败的文件
                m_Mono.StartCoroutine(StartDownLoadAB(callBack, downLoadList));
            }
        }
    }

    Patch FindPatchByGamePath(string name)
    {
        Patch patch = null;
        m_DownLoadDic.TryGetValue(name, out patch);
        return patch;
    }
}

public class FileTool
{
    /// <summary>
    /// 创建文件
    /// </summary>
    /// <param name="filePath"></param>
    /// <param name="bytes"></param>
    public static void CreateFile(string filePath,byte[] bytes)
    {
        if (File.Exists(filePath))
        {
            File.Delete(filePath);
        }
        FileInfo fileInfo = new FileInfo(filePath);
        Stream stream = fileInfo.Create();
        stream.Write(bytes,0,bytes.Length);
        stream.Close();
        stream.Dispose();
    }
}